package com.glidebitmappool.internal;

import ohos.media.image.PixelMap;
import ohos.media.image.common.PixelFormat;
import ohos.media.image.common.Size;

import java.util.HashMap;
import java.util.Map;
import java.util.NavigableMap;
import java.util.TreeMap;

/**
 * 位图尺寸配置类
 *
 * @author renpengfei
 * @since 2021-03-12
 */
public class SizeConfigStrategy implements LruPoolStrategy {
    private static final int MAX_SIZE_MULTIPLE = 8;
    private static final int TWO = 2;

    // The value returned by Bitmaps with the hidden Bitmap config.
    private static final PixelFormat[] ARGB_8888_IN_CONFIGS =
            new PixelFormat[]{PixelFormat.ARGB_8888, null,};

    // We probably could allow ARGB_4444 and RGB_565 to decode into each other, but ARGB_4444 is
    // deprecated and we'd rather be safe.
    private static final PixelFormat[] RGB_565_IN_CONFIGS =
            new PixelFormat[]{PixelFormat.RGB_565};
//    private static final PixelFormat[] ARGB_4444_IN_CONFIGS =
//            new PixelFormat[]{PixelFormat.ARGB_4444};
//    private static final PixelFormat[] ALPHA_8_IN_CONFIGS =
//            new PixelFormat[]{PixelFormat.ALPHA_8};

    private final KeyPool keyPool = new KeyPool();
    private final GroupedLinkedMap<Key, PixelMap> groupedMap = new GroupedLinkedMap<>();
    private final Map<PixelFormat, NavigableMap<Integer, Integer>> sortedSizes = new HashMap<>();

    /**
     * 添加图片
     *
     * @param bitmap 图片
     */
    @Override
    public void put(PixelMap bitmap) {
        int size = Util.getBitmapByteSize(bitmap);
        Key key = keyPool.get(size, bitmap.getImageInfo().pixelFormat);

        groupedMap.put(key, bitmap);

        NavigableMap<Integer, Integer> sizes = getSizesForConfig(bitmap.getImageInfo().pixelFormat);
        Integer current = sizes.get(key.size);
        sizes.put(key.size, current == null ? 1 : current + 1);
    }

    /**
     * 获取图片
     *
     * @param width 宽
     * @param height 高
     * @param config 格式
     * @return PixelMap
     */
    @Override
    public PixelMap get(int width, int height, PixelFormat config) {
        int size = Util.getBitmapByteSize(width, height, config);
        Key bestKey = findBestKey(size, config);

        PixelMap result = groupedMap.get(bestKey);
        if (result != null) {
            // Decrement must be called before reconfigure.
            decrementBitmapOfSize(bestKey.size, result);
            Size infoSize = result.getImageInfo().size;
            infoSize.width = width;
            infoSize.height = height;
            result.resetConfig(infoSize, result.getImageInfo().pixelFormat != null
                    ? result.getImageInfo().pixelFormat : PixelFormat.ARGB_8888);
        }
        return result;
    }

    /**
     * 设置key
     *
     * @param size
     * @param config
     * @return Key
     */
    private Key findBestKey(int size, PixelFormat config) {
        Key result = keyPool.get(size, config);
        for (PixelFormat possibleConfig : getInConfigs(config)) {
            NavigableMap<Integer, Integer> sizesForPossibleConfig = getSizesForConfig(possibleConfig);
            Integer possibleSize = sizesForPossibleConfig.ceilingKey(size);
            if (possibleSize != null && possibleSize <= size * MAX_SIZE_MULTIPLE) {
                if (possibleSize != size
                        || (possibleConfig == null ? config != null : !possibleConfig.equals(config))) {
                    keyPool.offer(result);
                    result = keyPool.get(possibleSize, possibleConfig);
                }
                break;
            }
        }
        return result;
    }

    @Override
    public PixelMap removeLast() {
        PixelMap removed = groupedMap.removeLast();
        if (removed != null) {
            int removedSize = Util.getBitmapByteSize(removed);
            decrementBitmapOfSize(removedSize, removed);
        }
        return removed;
    }

    /**
     * 减少位图大小
     *
     * @param size
     * @param removed
     * @throws NullPointerException
     */
    private void decrementBitmapOfSize(Integer size, PixelMap removed) {
        PixelFormat config = removed.getImageInfo().pixelFormat;
        NavigableMap<Integer, Integer> sizes = getSizesForConfig(config);
        Integer current = sizes.get(size);
        if (current == null) {
            throw new NullPointerException("Tried to decrement empty size"
                    + ", size: " + size
                    + ", removed: " + logBitmap(removed)
                    + ", this: " + this);
        }

        if (current == 1) {
            sizes.remove(size);
        } else {
            sizes.put(size, current - 1);
        }
    }

    /**
     * 获取配置大小
     *
     * @param config
     * @return NavigableMap
     */
    private NavigableMap<Integer, Integer> getSizesForConfig(PixelFormat config) {
        NavigableMap<Integer, Integer> sizes = sortedSizes.get(config);
        if (sizes == null) {
            sizes = new TreeMap<>();
            sortedSizes.put(config, sizes);
        }
        return sizes;
    }

    /**
     * 日志信息
     *
     * @param bitmap
     * @return String
     */
    @Override
    public String logBitmap(PixelMap bitmap) {
        int size = Util.getBitmapByteSize(bitmap);
        return getBitmapString(size, bitmap.getImageInfo().pixelFormat);
    }

    /**
     * 日志信息
     *
     * @param width 宽
     * @param height 高
     * @param config 格式
     * @return String
     */
    @Override
    public String logBitmap(int width, int height, PixelFormat config) {
        int size = Util.getBitmapByteSize(width, height, config);
        return getBitmapString(size, config);
    }

    /**
     * 获取大小
     *
     * @param bitmap
     * @return int
     */
    @Override
    public int getSize(PixelMap bitmap) {
        return Util.getBitmapByteSize(bitmap);
    }

    @Override
    public String toString() {
        StringBuilder sb =
                new StringBuilder()
                        .append("SizeConfigStrategy{groupedMap=")
                        .append(groupedMap)
                        .append(", sortedSizes=(");
        for (Map.Entry<PixelFormat, NavigableMap<Integer, Integer>> entry : sortedSizes.entrySet()) {
            sb.append(entry.getKey()).append('[').append(entry.getValue()).append("], ");
        }
        if (!sortedSizes.isEmpty()) {
            sb.replace(sb.length() - TWO, sb.length(), "");
        }
        return sb.append(")}").toString();
    }

    /**
     *
     */
    static class KeyPool extends BaseKeyPool<Key> {
        public Key get(int size, PixelFormat config) {
            Key result = get();
            result.init(size, config);
            return result;
        }

        /**
         * 初始化
         *
         * @return Key
         */
        @Override
        protected Key create() {
            return new Key(this);
        }
    }

    /**
     * 测试使用
     *
     * @since 2021-03-12
     */
    static final class Key implements Poolable {
        private final KeyPool pool;

        private int size;
        private PixelFormat config;

        Key(KeyPool pool) {
            this.pool = pool;
        }

        /**
         * Visible for testing.
         *
         * @param pool
         * @param size
         * @param config
         */
        Key(KeyPool pool, int size, PixelFormat config) {
            this(pool);
            init(size, config);
        }

        void init(int size1, PixelFormat pixelFormat) {
            this.size = size1;
            this.config = pixelFormat;
        }

        @Override
        public void offer() {
            pool.offer(this);
        }

        @Override
        public String toString() {
            return getBitmapString(size, config);
        }

        @Override
        public boolean equals(Object object) {
            if (object instanceof Key) {
                Key other = (Key) object;
                return size == other.size
                        && Util.bothNullOrEqual(config, other.config);
            }
            return false;
        }

        @Override
        public int hashCode() {
            int result = size;
            result = 31 * result + (config != null ? config.hashCode() : 0);
            return result;
        }
    }

    /**
     * getBitmapString
     *
     * @param size
     * @param config
     * @return String
     */
    private static String getBitmapString(int size, PixelFormat config) {
        return "[" + size + "](" + config + ")";
    }

    /**
     * 获取配置
     *
     * @param requested
     * @return PixelFormat
     */
    private static PixelFormat[] getInConfigs(PixelFormat requested) {
        switch (requested) {
            case ARGB_8888:
                return ARGB_8888_IN_CONFIGS;
            case RGB_565:
                return RGB_565_IN_CONFIGS;
//            case ARGB_4444:
//                return ARGB_4444_IN_CONFIGS;
//            case ALPHA_8:
//                return ALPHA_8_IN_CONFIGS;
            default:
                return new PixelFormat[]{requested};
        }
    }
}

